package com.ls.spring.framework.context;

import com.ls.spring.framework.annotation.MyAutowired;
import com.ls.spring.framework.aop.MyJdkDynamicAopProxy;
import com.ls.spring.framework.aop.config.MyAopConfig;
import com.ls.spring.framework.aop.support.MyAdvisedSupport;
import com.ls.spring.framework.beans.MyBeanWrapper;
import com.ls.spring.framework.beans.config.MyBeanDefinition;
import com.ls.spring.framework.beans.support.MyBeanDefinitionReader;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.util.*;

/**
 * 职责：完成Bean的创建和DI
 *
 * @author 挥之以墨
 */
public class MyApplicationContext {

    /**
     * Bean配置读取器
     */
    private MyBeanDefinitionReader reader;
    /**
     * BeanDefinition缓存
     */
    private Map<String, MyBeanDefinition> beanDefinitionMap = new HashMap<>();
    /**
     * IoC容器
     */
    private Map<String, MyBeanWrapper> factoryBeanInstanceCache = new HashMap<>();
    /**
     * 原对象缓存
     */
    private Map<String, Object> factoryBeanObjectCache = new HashMap<>();

    public MyApplicationContext(String... configLocations) {
        // 1、加载配置文件
        reader = new MyBeanDefinitionReader(configLocations);
        // 2、解析配置文件，封装成BeanDefinition
        List<MyBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
        // 3、缓存BeanDefinition
        doRegistBeanDefinition(beanDefinitions);
        // 4、依赖注入
        doAutowired();
    }

    /**
     * 缓存beanDefinition
     *
     * @param beanDefinitions bean定义列表
     */
    private void doRegistBeanDefinition(List<MyBeanDefinition> beanDefinitions) {
        for (MyBeanDefinition beanDefinition : beanDefinitions) {
            // 检查是否重复定义
            String factoryBeanName = beanDefinition.getFactoryBeanName();
            if (beanDefinitionMap.containsKey(factoryBeanName)) {
                throw new RuntimeException("The" + factoryBeanName + "is exist");
            }
            beanDefinitionMap.put(factoryBeanName, beanDefinition);
            beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
        }
    }

    /**
     * 依赖注入，配置阶段
     */
    private void doAutowired() {
        for (Map.Entry<String, MyBeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {
            getBean(beanDefinitionEntry.getKey());
        }
    }

    /**
     * Bean实例化，DI从这个方法开始
     *
     * @param beanName 首字母小写
     * @return bean实例
     */
    public Object getBean(String beanName) {
        MyBeanWrapper wrapper = this.factoryBeanInstanceCache.get(beanName);
        if (wrapper == null) {
            // 1、获取BeanDefinition信息
            MyBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
            // 2、实例化
            Object instance = instantiateBean(beanName, beanDefinition);
            // 3、封装成BeanWrapper
            wrapper = new MyBeanWrapper(instance);
            // 4、保存到IoC容器
            this.factoryBeanInstanceCache.put(beanName, wrapper);
        }

        // 5、执行依赖注入
        populateBean(wrapper);

        return wrapper.getWrapperInstance();
    }

    /**
     * Bean实例化并获取bean
     *
     * @param beanClass bean的类型
     * @return bean 实例
     */
    public Object getBean(Class beanClass) {
        return getBean(beanClass.getName());
    }

    /**
     * 实例化bean
     *
     * @param beanName       首字母小写
     * @param beanDefinition bean定义对象
     * @return bean实例
     */
    private Object instantiateBean(String beanName, MyBeanDefinition beanDefinition) {
        String className = beanDefinition.getBeanClassName();
        Object instance = null;
        try {
            if (this.factoryBeanObjectCache.containsKey(beanName)) {
                return this.factoryBeanObjectCache.get(beanName);
            }
            // 实例化bean
            Class<?> clazz = Class.forName(className);
            instance = clazz.newInstance();

            //==================AOP开始=========================
            //1、加载AOP的配置文件
            MyAdvisedSupport config = instantionAopConfig(beanDefinition);
            config.setTarget(instance);
            config.setTargetClass(clazz);

            // 2、判断规则，要不要生成代理类，如果要就覆盖原生对象，如果不要就不做任何处理，返回原生对象
            if (config.pointCutMath()) {
                instance = new MyJdkDynamicAopProxy(config).getProxy();
            }
            //==================AOP结束=========================

            this.factoryBeanObjectCache.put(beanName, instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }

    /**
     * Aop配置初始化
     *
     * @param beanDefinition bean定义
     */
    private MyAdvisedSupport instantionAopConfig(MyBeanDefinition beanDefinition) {
        MyAopConfig config = new MyAopConfig();
        config.setPointCut(this.reader.getConfig().getProperty("pointCut"));
        config.setAspectClass(this.reader.getConfig().getProperty("aspectClass"));
        config.setAspectBefore(this.reader.getConfig().getProperty("aspectBefore"));
        config.setAspectAfter(this.reader.getConfig().getProperty("aspectAfter"));
        config.setAspectAfterThrow(this.reader.getConfig().getProperty("aspectAfterThrow"));
        config.setAspectAfterThrowingName(this.reader.getConfig().getProperty("aspectAfterThrowingName"));
        return new MyAdvisedSupport(config);
    }

    /**
     * 执行依赖注入
     *
     * @param beanWrapper bean适配器实例
     */
    private void populateBean(MyBeanWrapper beanWrapper) {
        Class<?> clazz = beanWrapper.getBeanClass();

        // 获取所有字段，扫描有@Autowired进行赋值（此处只实现filed注入）
        for (Field field : clazz.getDeclaredFields()) {
            if (!field.isAnnotationPresent(MyAutowired.class)) {
                continue;
            }
            MyAutowired autowired = field.getAnnotation(MyAutowired.class);
            // 有自定义beanName则使用自定义的，否则使用类名首字母小写
            String autowireBeanName = autowired.value().trim();
            if (StringUtils.isBlank(autowireBeanName)) {
                autowireBeanName = field.getType().getName();
            }

            // 使用反射为字段赋值获取实例
            MyBeanWrapper fieldBeanWrapper = this.factoryBeanInstanceCache.get(autowireBeanName);
            if (fieldBeanWrapper == null) {
                continue;
            }
            try {
                field.setAccessible(true);
                field.set(beanWrapper.getWrapperInstance(), fieldBeanWrapper.getWrapperInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    public int getBeanDefinitionCount() {
        return this.beanDefinitionMap.size();
    }

    public Set<String> getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet();
    }

    public Properties getConfig() {
        return this.reader.getConfig();
    }
}
